import java.util.ArrayList;
import java.util.HashMap;
import java.io.*;

/**
 * Class that parses instructions from files and 
 * creates the instructions.
 * @author davnils
 */
public class InstructionParser {
        private RandomAccessFile inputReader;
        private HashMap<String, Integer> labels;
	private int playerId;

	/**
	 * Opens the file specified by filename and saves the stream for 
	 * further use.
	 * @param filename the file with instructions.
	 * @param playerid the ID of the bot, between 1 and 9, inclusive.
	 * @throws Exception if error occurs.
	 */
        public InstructionParser(String filename, int playerId) throws Exception {
                labels = new HashMap<String, Integer>();
		this.playerId = playerId;
		
                try {
                        inputReader = new RandomAccessFile("Bots/" + filename, "r");
                }
                catch(FileNotFoundException e) {
			System.out.println("Failed to read " + filename);
                        throw new Exception();
                }
                catch(IOException e) {
                        throw new Exception();
                }
		System.out.println("Successfully opened " + filename);
        }

	/**
	 * Parses the instructions and saves them in "instructions".
	 * @param registers the registers.
	 * @param instructions array to be filled with instructions.
	 * @param onlyLabels only parse labels this round.
	 * @throws Exception if an error occured.
	 */
        private void parseInstructions(IntWrapper [] registers, ArrayList<InstructionInterface> instructions, boolean onlyLabels)
        throws Exception {

		System.out.println("In parseInstructions()");
                int instructionCount = 0;
                String line;
                while((line = inputReader.readLine()) != null) {
			System.out.println("--------------------");
			System.out.println("Reading (possibly) instruction " + instructionCount);
			System.out.println("Contents: " + line);
                        line = line.split("//")[0].trim().toLowerCase();
                        String [] items = line.split(" ");
                        if(items.length < 2) {
                                continue;
                        }

			items[0] = items[0].trim();

                        instructionCount++;
                        if(items[0].equals("lbl")) {
                        	instructionCount--;
				if(onlyLabels) {
                               		labels.put(items[1], instructionCount);
					System.out.println("Found label: \"" + items[1] + "\", associated with instruction number: " + (instructionCount));
				}
				continue;
                        }
			if(onlyLabels) {
				continue;
			}
	
                        if(items[0].equals("add")) {
                                instructions.add(new AddInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("sub")) {
                                instructions.add(new SubInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("mul")) {
                                instructions.add(new MulInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("div")) {
                                instructions.add(new DivInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("mov")) {
                                instructions.add(new MovInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("jmp")) {
                                instructions.add(new JmpInstruction(
                                                getArgument(items[1], registers, "s")));
                        }
                        else if(items[0].equals("jme")) {
                                instructions.add(new JmeInstruction(
                                                getArgument(items[1], registers, "r/n"),
                                                getArgument(items[2], registers, "r/n"),
                                                getArgument(items[3], registers, "s")));
                        }
                        else if(items[0].equals("jmn")) {
                                instructions.add(new JmnInstruction(
                                                getArgument(items[1], registers, "r/n"),
                                                getArgument(items[2], registers, "r/n"),
                                                getArgument(items[3], registers, "s")));
                        }
                        else if(items[0].equals("call")) {
                                instructions.add(new CallInstruction(
                                                getArgument(items[1], registers, "s")));
                        }
                        else if(items[0].equals("ret")) {
                                instructions.add(new RetInstruction());
                        }
                        else if(items[0].equals("cmp")) {
                                instructions.add(new CmpInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n"),
                                                getArgument(items[3], registers, "r/n")));
                        }
                        else if(items[0].equals("info")) {
                                ArrayList<IntWrapper> args = new ArrayList<IntWrapper>();
                                switch(items.length) {
                                case 2:
                                        args.add(getArgument(items[1], registers, "r"));
                                        break;
                                case 3:
                                        args.add(getArgument(items[1], registers, "r"));
                                        args.add(getArgument(items[2], registers, "r"));
                                        break;
                                case 4:
                                        args.add(getArgument(items[1], registers, "r"));
                                        args.add(getArgument(items[2], registers, "r/n"));
                                        args.add(getArgument(items[3], registers, "r/n"));
                                        break;
                                }
				args.add(new IntWrapper(playerId));
                                instructions.add(new InfoInstruction(
                                                args));
                        }
                        else if(items[0].equals("in")) {
                                instructions.add(new InOutInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
                        else if(items[0].equals("out")) {
                                instructions.add(new InOutInstruction(
                                                getArgument(items[1], registers, "r"),
                                                getArgument(items[2], registers, "r/n")));
                        }
			else {
				System.out.println("Could not find instruction " + items[0]);
				instructionCount--;
				continue;
			}
			System.out.println("Added instruction");
			System.out.println("--------------------");
                }
		System.out.println("Read a total of " + instructionCount + " instructions");
        }

	/**
	 * Reads an argument and checks the syntax.
	 * @param arg the actual data to be checked.
	 * @param registers the registers.
	 * @param syntax describes the valid values of arg.
	 * @return An IntWrapper referring to a register or a value.
	 * @throws Exception if an error occured.
	 */
        private IntWrapper getArgument(String arg, IntWrapper [] registers , String syntax) throws Exception {
                if(arg == null) {
			System.out.println("Bad argument to instruction");
                        throw new Exception();
                }
		System.out.println("Reading argument: " + arg);

                String [] options = syntax.split("/");
                for(String option : options) {
			System.out.println("Reading option: " + option);
                        if(option.equals("r")) {
                                if(arg.charAt(0) == 'r') {
					int registerIndex = Integer.parseInt(arg.substring(1));
					System.out.println("Reading register r" + registerIndex);
                                        IntWrapper ret = registers[registerIndex];
                                        if(ret == null) {
							System.out.println("Failed to read register");
                                                        throw new Exception();
                                        }
                                        return ret;
                                }
                        }
                        else if(option.equals("n")) {
				System.out.println("Reading Integer");
                                return new IntWrapper(Integer.parseInt(arg));
                        }
                        else if(option.equals("s")) {
				System.out.println("Reading label " + arg + ", bound to instruction " + labels.get(arg));
                                return new IntWrapper(labels.get(arg).intValue());
                        }
                }
		return null;
        }

	/**
	 * Public wrapper that parses labels and instructions.
	 * @param registers the registers.
	 * @return array of instructions.
	 * @throws Exception if an error occured. 
	 */
        public ArrayList<InstructionInterface> parseFile(IntWrapper [] registers) throws Exception {
                ArrayList<InstructionInterface> instructions = new ArrayList<InstructionInterface>();
                try {
                        parseInstructions(registers, instructions, true);
			inputReader.seek(0);
                        parseInstructions(registers, instructions, false);
                }
                catch(Exception e) {
			System.out.println("Failed to parse file");
                        return null;
                }

                return instructions;
        }
}
